﻿using NLog;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using TiaoTiaoCode.NLogger;
using TiaoTiaoCode.NLogger.Dtos;
using TiaoTiaoCode.NLogger.Enums;

namespace TiaoTiaoCode.BasicDbCore
{
    public sealed class DbFramework
    {
        #region

        const string LoggerName = "DbLogger";
        #endregion

        #region 连接信息

        /// <summary>
        /// 本地数据库连接串
        /// </summary>
        public static string Connection { get; set; } = default!;

        /// <summary>
        /// 本地数据库连接类型
        /// </summary>
        public static DbType DbType { get; set; } = DbType.Sqlite;

        /// <summary>
        /// 是否使用日志
        /// </summary>
        public static bool UseFileLog { get; set; } = true;

        /// <summary>
        /// 是否使用数据库日志
        /// </summary>
        public static bool UseDbLog { get; private set; } = false;

        #endregion

        static DbFramework()
        {
            TiaoTiaoNLogger.AddFileTarget(LoggerName, "dbDebug", LogLevel.Debug, DbType.ToString());

            TiaoTiaoNLogger.SetLogger(LoggerName);
        }

        /// <summary>
        /// 数据操作对象
        /// </summary>
        private static ISqlSugarClient? _scope;
        public static ISqlSugarClient Scope
        {
            get
            {
                return _scope ??= new SqlSugarScope(new ConnectionConfig
                {
                    DbType = DbType,
                    ConnectionString = Connection,
                    IsAutoCloseConnection = true,
                    AopEvents = new AopEvents
                    {
                        OnLogExecuting = OnLogExcuting,
                        OnDiffLogEvent = DiffLogAct
                    },
                    ConfigureExternalServices = new ConfigureExternalServices
                    {
                        EntityService = (c, p) =>
                        {
                            if (!string.IsNullOrWhiteSpace(p.DefaultValue))
                            {
                                var tmp = p.DefaultValue;
                                p.DefaultValue = string.Empty;
                            }
                        }
                    }
                });
            }
        }

        #region 内部日志 

        /// <summary>
        /// 设置启用操作日志到数据库
        /// </summary>
        /// <param name="logTableName">日志表名</param>
        public static void SetUseDbLog(string logTableName = "sys_log")
        {
            var nlogDbType = DbType switch
            {
                DbType.Sqlite => NLogDbType.Sqlite,
                DbType.MySql => NLogDbType.MySql,
                DbType.SqlServer => NLogDbType.SqlServer,
                _ => NLogDbType.SqlServer
            };

            TiaoTiaoNLogger.AddDatabaseTarget(LoggerName,
                        nlogDbType,
                        Connection,
                        $@"insert into {logTableName}(id,log_data,method,table_name,log_date,operate_type) values(@Id,@LogData,@Method,@TableName,@LogDate,@OperateType)",
                        typeof(DbLogItem));

            TiaoTiaoNLogger.SetLogger(LoggerName);

            UseDbLog = true;
        }

        /// <summary>
        /// sql日志
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="pars"></param>
        static void OnLogExcuting(string sql, SugarParameter[] pars)
        {
            if (pars != null && pars.Length > 0)
                foreach (var p in pars)
                {
                    sql = sql.Replace(p.ParameterName, p.Value?.ToString());
                }

            if (UseFileLog)
                TiaoTiaoNLogger.LogDebug(sql, LoggerName);
        }

        /// <summary>
        /// 数据变化写入日志表
        /// </summary>
        /// <param name="item"></param>
        static void DiffLogAct(DiffLogModel item)
        {
            var bfData = item.BeforeData;
            var afData = item.AfterData;

            if (item.DiffType == DiffType.insert)
            {
                var afDataColumn = afData[0].Columns;

                var log = new DbLogItem
                {
                    Id = SnowFlakeSingle.instance.NextId().ToString(),
                    Method = item.DiffType.ToString(),
                    TableName = afData.First().TableName,
                    OperateType = item.DiffType.ToString(),
                    LogData = afDataColumn.Aggregate("", (a, b) => a += $"{b.ColumnName}=>{b.Value},").TrimEnd(',')
                };

                TiaoTiaoNLogger.LogDb(log, LoggerName);
            }
            else if (item.DiffType == DiffType.update)
            {
                if (bfData.Count <= 0) return;
                var bfDataColumn = bfData[0].Columns;
                var afDataColumn = afData.Count <= 0 ? null : afData[0].Columns;

                var diffs = new List<string>
                {
                    $"主键：【{bfDataColumn.Where(t => t.IsPrimaryKey).First().Value}】"
                };

                bfDataColumn.ForEach(bf =>
                {
                    if (afDataColumn == null) return;
                    var af = afDataColumn.Where(t => t.ColumnName == bf.ColumnName).FirstOrDefault();
                    if (af == null) return;

                    if (bf.ColumnName.ToUpper() == "UpdateTime".ToUpper()) return;

                    switch (bf.Value.GetType().Name.ToUpper())
                    {
                        case "STRING":
                            if (af.Value.ToString() == bf.Value.ToString()) return;
                            break;
                        case "INT32":
                            if (int.TryParse(af.Value.ToString(), out var a)
                                && int.TryParse(bf.Value.ToString(), out var b))
                                if (a == b) return;
                            break;
                        case "DATETIME":
                            if (DateTime.TryParse(af.Value.ToString(), out var dta)
                                && DateTime.TryParse(bf.Value.ToString(), out var dtb))
                                if (dta == dtb) return;
                            break;
                        case "DECIMAL":
                            if (decimal.TryParse(af.Value.ToString(), out var da)
                                && decimal.TryParse(bf.Value.ToString(), out var db))
                                if (da == db) return;
                            break;
                    }

                    if (bf.Value != af.Value)
                        diffs.Add($"{bf.ColumnName}:【{bf.Value}】->【{af.Value}】");
                });

                if (diffs.Count > 0)
                {
                    var log = new DbLogItem
                    {
                        Id = SnowFlakeSingle.instance.NextId().ToString(),
                        Method = item.DiffType.ToString(),
                        TableName = bfData.First().TableName,
                        OperateType = "update",
                        LogData = diffs.Aggregate("", (a, b) => a += $"{b},").TrimEnd(',')
                    };


                    if (UseDbLog)
                        TiaoTiaoNLogger.LogDb(log, LoggerName);
                }
            }
            else if (item.DiffType == DiffType.delete)
            {
                if (bfData.Count <= 0) return;

                var bfDataColumn = bfData[0].Columns;
                var log = new DbLogItem
                {
                    Id = SnowFlakeSingle.instance.NextId().ToString(),
                    Method = item.DiffType.ToString(),
                    TableName = bfData.First().TableName,
                    OperateType = "delete",
                    LogData = bfDataColumn.Aggregate("", (a, b) => a += $"{b.ColumnName}=>{b.Value},").TrimEnd(',')
                };

                if (UseDbLog)
                    TiaoTiaoNLogger.LogDb(log, LoggerName);
            }
        }

        #endregion

        #region DbCore 初始化

        /// <summary>
        /// DbCore 初始化
        /// </summary>
        /// <param name="assemblyName">数据库表对象所在程序集名称</param>
        /// <param name="entitiesNamespace">数据库表对象所有命名空间</param>
        /// <param name="createDatabase">是否创建数据库</param>
        public static void AddDbCoreStartup(string assemblyName, string entitiesNamespace, bool createDatabase = true)
        {
            if (createDatabase)
                Scope.DbMaintenance.CreateDatabase();

            var types = Assembly.Load(assemblyName).GetTypes()
                            .Where(x => x.FullName!.Contains(entitiesNamespace))
                            .ToArray();

            Scope.CodeFirst.InitTables(types);
        }

        /// <summary>
        /// DbCore 初始化
        /// </summary>
        /// <param name="assemblyName">数据库表对象所在程序集名称</param>
        /// <param name="entitiesNamespace">数据库表对象所有命名空间</param>
        /// <param name="createDatabase">是否创建数据库</param>
        public static void AddDbCoreStartupByFile(string assemblyName, string entitiesNamespace, bool createDatabase = true)
        {
            if (createDatabase)
                Scope.DbMaintenance.CreateDatabase();

            var types = Assembly.LoadFrom(assemblyName).GetTypes()
                            .Where(x => x.FullName!.Contains(entitiesNamespace))
                            .ToArray();

            Scope.CodeFirst.InitTables(types);
        }

        #endregion
    }
}
